package util

import (
	"cke.io/ckectl/app/apis/v1alpha1"
	"cke.io/ckectl/app/constants"
	"encoding/base64"
	"fmt"
	"github.com/sirupsen/logrus"
	"io/ioutil"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strconv"
	"strings"
	"text/template"
)

type DataSource struct {
	CkeClusterSpec     v1alpha1.CkeClusterSpec
	CkeNode            []v1alpha1.CkeNode
	CurrentCkeNode     v1alpha1.CkeNode
	CkeComponentSpec   v1alpha1.CkeComponentSpec
	CkeComponentStatus v1alpha1.CkeComponentStatus
	PrivateKey         string
}

//store key into a file, return private content
func GetPrivateKeyContent(key string) (string, error) {
	if content, err := base64.StdEncoding.DecodeString(key); err != nil {
		return "", err
	} else {
		return string(content), nil
	}
}

func ProcessTemplate(cluster v1alpha1.CkeCluster, component v1alpha1.CkeComponent, nodes []v1alpha1.CkeNode, curNode v1alpha1.CkeNode, path, key string) error {
	supportedFiles := []string{
		".yaml", ".yml", ".sh", ".j2", ".conf", ".cfg", ".json", ".md",
		".py", ".service", ".txt", ".toml", ".tmpl",
	}

	var data DataSource
	data.CkeClusterSpec = cluster.Spec
	sort.Sort(CkeNodeSort(nodes))
	data.CkeNode = nodes
	data.CkeComponentSpec = component.Spec
	data.CurrentCkeNode = curNode
	privateKey, _ := GetPrivateKeyContent(key)
	data.PrivateKey = privateKey
	//file, err := ioutil.ReadFile(constants.RootPrivateKey)
	//if err != nil {
	//	logrus.Errorf("Read key file failed: %s", err)
	//	return err
	//}
	//data.PrivateKey = string(file)

	//read script files from path
	files, err := ListAllFilesInPath(path)
	if err != nil {
		logrus.Errorf("failed to list template files: %s", err.Error())
		return err
	}

	files = FilterSupportedFiles(supportedFiles, files)

	funcMap := map[string]interface{}{
		"default":        Default,
		"split":          strings.Split,
		"string2bool":    String2Bool,
		"string2int":     String2Int,
		"string2array":   String2Array,
		"replace":        strings.ReplaceAll,
		"versionCompare": VersionCompare,
		"getArch":        GetArch,
		"getNodeByRole":  GetNodeByRole,
	}

	fileMap := make(map[string]string, 0)
	for _, file := range files {
		fileBytes, err := ioutil.ReadFile(filepath.Clean(file))
		if err != nil {
			logrus.Errorf("failed to read file: %s, error: %s", file, err.Error())
			return err
		}
		singleTemplate := string(fileBytes)
		t := template.New("Single file template").Funcs(template.FuncMap(funcMap))
		t, err = t.Parse(singleTemplate)
		if err != nil {
			logrus.Errorf("failed to parse template: %s, error: %s", file, err.Error())
			return err
		}

		tmpFile := fmt.Sprintf("%s.generated.tmp", file)
		fileWriter, err := os.OpenFile(filepath.Clean(tmpFile), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600)
		if err != nil {
			if fileWriter != nil {
				_ = fileWriter.Close()
			}
			logrus.Errorf("failed to open file %s with error %s", tmpFile, err.Error())
			return err
		}

		err = t.Execute(fileWriter, data)
		if err != nil {
			if fileWriter != nil {
				_ = fileWriter.Close()
			}
			logrus.Errorf("failed to execute template: %s", err.Error())
			return err
		}
		if fileWriter != nil {
			_ = fileWriter.Close()
		}
		fileMap[file] = tmpFile
	}

	for srcFile, genFile := range fileMap {
		err = os.Remove(srcFile)
		if err != nil {
			logrus.Errorf("failed to remove file %s, skipped...", srcFile)
			continue
		} else {
			err = os.Rename(genFile, srcFile)
			if err != nil {
				logrus.Errorf("failed to rename file %s, skipped...", genFile)
				continue
			}
		}
	}
	return nil
}

func ListAllFilesInPath(filedir string) (files []string, err error) {
	err = filepath.Walk(filedir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() {
			files = append(files, path)
		}
		return nil
	})

	if err == nil && len(files) == 0 {
		err = fmt.Errorf("no files find in path %s", filedir)
	}
	return
}

func Default(def string, v interface{}) string {
	if v == nil || v.(string) == "" {
		return def
	}
	return v.(string)
}

func String2Bool(str string) bool {
	if strings.ToLower(str) == "true" {
		return true
	} else if strings.ToLower(str) == "false" {
		return false
	}
	return false
}

func String2Int(str string) int {
	res, err := strconv.Atoi(str)
	if err != nil {
		return 0
	} else {
		return res
	}
}

func String2Array(str string) []string {
	return strings.Split(str, ",")
}

func VersionCompare(v1, v2 string) int {
	return Compare(v1, v2)
}

func GetArch() string {
	return constants.ARCH
}

func GetNodeByRole(nodes []v1alpha1.CkeNode, role string) []v1alpha1.CkeNode {
	var result []v1alpha1.CkeNode
	for _, node := range nodes {
		if InArray(role, node.Spec.Roles) {
			result = append(result, node)
		}
	}
	return result
}

func InArray(obj string, array []string) bool {
	for _, item := range array {
		if obj == item {
			return true
		}
	}
	return false
}

func FilterSupportedFiles(types, files []string) []string {
	result := make([]string, 0)
	for _, file := range files {
		fileType := path.Ext(file)
		if InArray(fileType, types) {
			result = append(result, file)
		}
	}
	return result
}
